package com.common.print;

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @ClassName Mutex
 * @Description 自定同步队列 并实现循环打印ABC
 * @Author admin
 * @Date 2021/4/27 9:53
 * @Version 1.0
 **/
@Slf4j
public class AbstractQueuedSynchronizedDemo implements Lock {

    /**
     * @Description: 自定义同步器
     * @Author: liuhuanjin
     * @Date: 2021/4/27 9:54
     **/
    private static class Sync extends AbstractQueuedSynchronizer{
        // 判断当前线程是否被独占
        @Override
        protected boolean isHeldExclusively() {
            return getState() ==1;
        }

        // 尝试获取锁(独占锁)
        @Override
        protected boolean tryAcquire(int arg) {
            // 有且只有当state为0 且成功将state更新为1之后 当前线程才可以获取独占锁
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 尝试释放锁(独占锁)
        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == 0){
                // 同步块中没有锁 无需释放
                throw new IllegalMonitorStateException();
            }

            // 将独占线程设为null
            setExclusiveOwnerThread(null);

            // 将state设为0 以便其他线程可以获取到锁
            setState(0);
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

    // 将操作代理到 Sync 上
    private final Sync sync = new Sync();

    // 获取独占锁 如果当前线程成功获取锁 那么方法就返回 否则会将当前线程放入同步队列等待
    // 该方法会调用重写的tryAcquire()方法判断是否可以获取锁
    @Override
    public void lock() {
        sync.acquire(1);
    }


    // acquireInterruptibly和acquire相同 但是该方法响应中断 当线程在同步队列中等待时 如果线程被中断 会抛出InterruptedException并返回
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    // 在acquireInterruptibly的基础尚添加了超时控制 同时支持超时和中断 当在指定时间内获取到锁返回true 否则返回false
    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @NotNull
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    // 当前同步块中是否存在等待线程
    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    private static Integer num = 1;
    public static void main(String[] args) throws InterruptedException {
        final AbstractQueuedSynchronizedDemo abstractQueuedSynchronizedDemo = new AbstractQueuedSynchronizedDemo();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3;) {
                    try {
                        abstractQueuedSynchronizedDemo.lock();
                        while (num == 1){
                            System.out.println("A");
                            num = 2;
                            // 在此处i++是因为只有在获取到锁后才可以打印 外层的循环是为了打印次数 所以只有获取到锁之后才可以将打印次数+1
                            // 当此线程未获取到锁 i永远只是上一个值 不会+1
                            i++;
                        }
                    }finally {
                        abstractQueuedSynchronizedDemo.unlock();
                    }
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3;) {
                    try {
                        abstractQueuedSynchronizedDemo.lock();
                        while (num == 2){
                            System.out.println("B");
                            num = 3;
                            i++;
                        }
                    }finally {
                        abstractQueuedSynchronizedDemo.unlock();
                    }
                }
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3;) {
                    try {
                        abstractQueuedSynchronizedDemo.lock();
                        while (num == 3){
                            System.out.println("C");
                            num = 1;
                            i++;
                        }
                    }finally {
                        abstractQueuedSynchronizedDemo.unlock();
                    }
                }
            }
        });


        threadA.start();
        threadB.start();
        threadC.start();


    }

}
